/*+***********************************************************************************
 Filename: 03_tinycore_step02\src\core.v
 Description: demo the construction of a simple tiny-core.
              step02: a core can execute ADI and JAL.

 Modification:
   2022.10.13 Creation   H.Zheng
   2022.11.01 add LOAD/Store and RAM port
   2022.11.07 add peripheral bus
   2022.11.19 change memory map, change ram_addr to 10bits
   2022.11.22 add ret and bxx、lui、li
   2022.12.07 add interrupt

Copyright (C) 2022  Zheng Hui (hzheng@gzhu.edu.cn)

License: MulanPSL-2.0

Thanks: some codes are learned from Bob Hu's E203 Hummingbird project source. 

***********************************************************************************-*/



module core (
    input wire core_clk,
    input wire reset_n,
    //instruction ROM
    output wire [31:0] ibus_addr,
    output wire ibus_re,  //read enable
    input wire [31:0] instruction,
    //data RAM
    output wire ram_ce, //chip enable
    output wire ram_wre, //write enable
    output wire [9:0] ram_addr, //address bus
    input wire [31:0] ram_data_in,
    output wire [31:0] ram_data_out,
    //peripheral
    output wire peripheral_ce, //chip enable
    output wire peripheral_wre, //write enable
    output wire [8:0] peripheral_addr, //address bus
    input wire [31:0] peripheral_data_in,
    output wire [31:0] peripheral_data_out,
    //interrupt
    input wire ext_int,
    output wire int_active,
    //
    output wire [31:0] monitor_port
);

    //PC
    reg [31:0] program_counter;
    wire [31:0] pc_next;
    wire [31:0] pc_next_with_int;

    always @(posedge core_clk or negedge reset_n) begin
        if (~reset_n) begin
            program_counter <= 32'b0;
        end
        else begin
//            program_counter <= pc_next;
            program_counter <= pc_next_with_int;
        end
    end

    assign ibus_addr = program_counter>>2;
    assign ibus_re = 1'b1;

    //
    //decoder
    //ADI and J only.

    wire [6:0] opcode = instruction[6:0];
    wire [2:0] funct3 = instruction[14:12];
    wire [11:0] funct12 = instruction[31:20];   

    wire opcode_OP_IMM = (opcode == 7'b0010011);
    wire opcode_JAL    = (opcode == 7'b1101111);
    wire opcode_BRANCH = (opcode == 7'b1100011);
    wire opcode_SYSTEM = (opcode == 7'b1110011);

    wire [4:0] i_rd_idx = instruction[11:7];
    wire [4:0] i_rs1_idx = instruction[19:15];
    wire [31:0] i_imm_J = {{12{instruction[31]}},instruction[19:12],instruction[20],instruction[30:21],1'b0};
    wire [31:0] i_imm_I = {{21{instruction[31]}},instruction[30:20]};
    wire [31:0] i_imm_U = {instruction[31:12], 12'b0};
    wire [31:0] i_imm_B = {{20{instruction[31]}},instruction[7],instruction[30:25],instruction[11:8],1'b0};

    wire inst_ADI = opcode_OP_IMM & (funct3 == 3'b000);
    wire inst_JAL = opcode_JAL;
    wire inst_J = inst_JAL & (i_rd_idx == 5'b0);
    wire inst_LUI = (opcode == 7'b0110111);
    wire inst_JALR = (opcode == 7'b1100111);
    wire inst_BNE = opcode_BRANCH & (funct3 == 3'b001);

    wire inst_MRET = opcode_SYSTEM & (funct12 == 12'b001100000010);

    wire [4:0] i_rs2_idx = instruction[24:20]; 
    //
    //add LW and SW (2022.11.01)
    wire opcode_LOAD = (opcode == 7'b0000011);
    wire opcode_STORE = (opcode == 7'b0100011);

    wire inst_LW = opcode_LOAD & (funct3 == 3'b010);
    wire inst_SW = opcode_STORE & (funct3 == 3'b010);

    wire [31:0] i_imm_S = {{21{instruction[31]}},instruction[30:25],instruction[11:7]};
    //addr: 
    wire [31:0] loadstore_addr = (inst_LW) ? rf_rs1data + i_imm_I :
                                 (inst_SW) ? rf_rs1data + i_imm_S : 32'b0;

    //to use the 12bit signed imm offset to address the data for optimization of the code
    //assign addr for peripheral at         0x????0000 - 0x????03ff (1kB)
    //assign addr for data ram at           0x????0400 - 0x????13ff (4kB)
//    assign ram_ce = (loadstore_addr[31:10] == 22'b0) & (opcode_LOAD | opcode_STORE);
    assign peripheral_ce = (loadstore_addr[15:10] == 6'b0) & (opcode_LOAD | opcode_STORE);
    assign ram_ce = (loadstore_addr[15:13] == 3'b0) & (loadstore_addr[12:10] != 3'b0) & (opcode_LOAD | opcode_STORE);

//    assign ram_wre = inst_SW;
    assign ram_wre = opcode_STORE & (~core_clk);

//    assign ram_addr = loadstore_addr[10:2]; //loadstore_addr is in bytes but ram_addr is in words
    assign ram_addr = loadstore_addr[11:2]; //loadstore_addr is in bytes but ram_addr is in words

    //output data from rs2
    assign ram_data_out = rf_rs2data;
    

    //execution of ADI
    //

    //fetch rs1&rs2 value from Register File
    wire[31:0] rf_rs1data;
    wire[31:0] rf_rs2data;
    wire rf_rdwen;
    wire[31:0] rf_rd_data;
    wire[31:0] x16_value;
    
    //register file module
    regfile m_regfile(
      .reset_n(reset_n),
      .i_rf_rs1idx(i_rs1_idx),
      .i_rf_rs2idx(i_rs2_idx),
      .rf_rs1data(rf_rs1data),
      .rf_rs2data(rf_rs2data),
      .i_rf_rdidx(i_rd_idx),
      .i_rf_rdwen(rf_rdwen),
      .i_rf_rd_data(rf_rd_data),
      .i_rf_rd_wr_clk(~core_clk),
      .x16_value(x16_value)
    );

    //ADI operation
    wire [31:0] adi_result = rf_rs1data + i_imm_I;

    //LW operation

    //write back
    //rd source:
    // 1) from ALU result;
    // 2) from ram;
    // 3) from peripheral;  (2022.11.07)

//    assign rf_rd_data = adi_result;
//    assign rf_rdwen = inst_ADI;
//    assign rf_rd_data = (inst_ADI) ? adi_result :
//                          (inst_LW) ? ram_data_in : 32'b0;
    wire [31:0] load_result = (ram_ce & opcode_LOAD) ? ram_data_in :
                                 (peripheral_ce & opcode_LOAD) ? peripheral_data_in : 32'b0;

//    assign rf_rd_data = (inst_ADI) ? adi_result :
//                          (opcode_LOAD) ? load_result : 32'b0;
//    assign rf_rdwen = inst_ADI | opcode_LOAD;
    wire [31:0] pc_plus_four = program_counter + 4;
    assign rf_rd_data = (inst_ADI) ? adi_result :
                          (opcode_LOAD) ? load_result : 
                             ((inst_JAL | inst_JALR) &(i_rd_idx != 5'b0)) ? pc_plus_four :
                             (inst_LUI) ? i_imm_U : 32'b0;
    assign rf_rdwen = inst_ADI | opcode_LOAD | inst_LUI | ((inst_JAL | inst_JALR) &(i_rd_idx != 5'b0));

    //Jump
    //
    reg reset_n_sync;
    always @(posedge core_clk or negedge reset_n) begin
//    always @(posedge core_clk) begin
//        reset_n_sync <=reset_n;
        if (~reset_n) begin
            reset_n_sync <= 1'b0;
        end
        else begin
            reset_n_sync <= 1'b1;
        end
    end

    wire [31:0] rs1_plus_imm_i = rf_rs1data + i_imm_I;

    wire branch_in_effect = (inst_BNE & (rf_rs1data != rf_rs2data));

    assign pc_next = (~reset_n_sync) ? 32'b0 :
                        branch_in_effect ? program_counter + i_imm_B :
                        inst_JALR ? rs1_plus_imm_i :
                          inst_JAL ? program_counter + i_imm_J : 
                            inst_MRET ? csr_mepc : pc_plus_four;  
//    assign pc_next = (~reset_n_sync) ? 32'b0 :
//                          inst_JAL ? program_counter + i_imm_J : program_counter + 4;  

//    assign pc_next = inst_JAL ? program_counter + i_imm_J : program_counter + 4;  





//interrupt
//use FSM for implementation
//
    reg [31:0] csr_mepc;
    reg ext_int_in_effect;


    //FSM section 1
    localparam S_INT_IDLE = 0, S_INT_TRIGGERED = 1, S_INT_ACTIVE=2, S_INT_MRET=3;
    reg [1:0] current_state = 0, next_state;    

    always @(negedge core_clk or negedge reset_n) begin
        if (!reset_n) begin
            current_state <= S_INT_IDLE;
        end else begin
            current_state <= next_state;
        end
    end

    //FSM section 2

    //ctrl signal
    reg ctrl_int_triggered;
    reg ctrl_int_mret;

    always @(*) begin
        next_state = 0;
        ctrl_int_triggered = 0;
        ctrl_int_mret = 0;

        case (current_state) 
            S_INT_IDLE: begin
                if (ext_int) begin  
                    next_state <= S_INT_TRIGGERED;
                end else begin
                    next_state <= S_INT_IDLE;                        
                end
            end
            S_INT_TRIGGERED : begin
                ctrl_int_triggered = 1'b1;
                next_state <= S_INT_ACTIVE;
            end
            S_INT_ACTIVE : begin
                if (inst_MRET) begin
                    next_state <= S_INT_MRET;
                end else begin
                    next_state <= S_INT_ACTIVE;
                end
            end
            S_INT_MRET : begin
                ctrl_int_mret = 1'b1;
                next_state <= S_INT_IDLE;
            end
            default: begin
                next_state <= S_INT_IDLE;
            end
        endcase
    end

    //FSM section 3
	always @(posedge ctrl_int_triggered) begin
        csr_mepc <= pc_next;
    end

    wire [31:0]  isr_entrance = 32'h80002e00;
    assign pc_next_with_int = (ctrl_int_triggered) ? isr_entrance : pc_next;


   assign int_active = (current_state != S_INT_IDLE);

    //
    assign monitor_port = {ext_int, current_state, ibus_addr[1:0], x16_value[0]};
//    assign monitor_port = {ibus_addr[1:0], instruction[5:2]};
//    assign monitor_port = {ibus_addr[1:0], peripheral_ce, peripheral_addr[2], 2'b0};
//    assign monitor_port = {ibus_addr[1:0], x16_value[3:0]};
//    assign monitor_port = x16_value;
//    assign monitor_port = {ram_ce, ram_wre, ram_addr[1:0], ram_data_out[1:0]};
//    assign monitor_port = {inst_J, inst_SW, inst_ADI, inst_LW, ram_data_out[1:0]};
//    assign monitor_port = {inst_J, inst_SW, inst_ADI, inst_LW, x16_value[1:0]};
//    assign monitor_port = {inst_J, inst_SW, inst_ADI, inst_LW, ibus_addr[1:0]};
    //assign monitor_port = {inst_J, inst_ADI, ibus_addr[3:0]};
//    assign monitor_port = {rf_rdwen, rf_rdidx};
//    assign monitor_port = {rf_rdwen, i_imm_I[4:0]};


    //peripheral part
    //the ctrl-bus signal is simular to ram
    //since peripheral I/O registers are mapped to the same memory space with ram

    //assign addr for peripheral at         0x????0000 - 0x????03ff (1kB)
    //assign addr for data ram at           0x????0400 - 0x????13ff (4kB)
    //so that we can use the 12bit imm offset to address the data

    assign peripheral_wre = opcode_STORE & (~core_clk);

    assign peripheral_addr = {1'b0, loadstore_addr[9:2]}; //loadstore_addr is in bytes but peripheral_addr is in words

    //output data from rs2
    assign peripheral_data_out = rf_rs2data;
    


endmodule

